"""Property-based tests for AI generation functionality.

Feature: buyu-walking-app
Testing correctness properties related to AI content generation.
"""
import pytest
from hypothesis import given, strategies as st, settings, assume, HealthCheck
from unittest.mock import Mock, patch, MagicMock
from app.models.location import Location
from app.models.poi import POI
from app.models.weather_data import WeatherData
from app.services.ai_generation_service import AIGenerationService
from datetime import datetime


# Strategies for generating test data
@st.composite
def pois(draw):
    """Generate valid POI data."""
    poi = Mock(spec=POI)
    poi.id = draw(st.text(min_size=1, max_size=36, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd'))))
    poi.name = draw(st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Zs'))))
    poi.latitude = draw(st.floats(min_value=-90, max_value=90))
    poi.longitude = draw(st.floats(min_value=-180, max_value=180))
    poi.category = draw(st.sampled_from(['风景名胜', '公园广场', '文化场馆', '博物馆', '纪念馆', '历史遗迹', '自然景观']))
    poi.description = draw(st.text(min_size=10, max_size=200, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Zs', 'Nd', 'Po'))))
    poi.photos = []
    poi.rating = draw(st.one_of(st.none(), st.floats(min_value=0, max_value=5)))
    poi.tags = []
    
    return poi


@st.composite
def weather_data(draw):
    """Generate valid weather data."""
    weather = Mock(spec=WeatherData)
    weather.temperature = draw(st.floats(min_value=-30, max_value=45))
    weather.condition = draw(st.sampled_from(['晴', '多云', '阴', '小雨', '中雨', '大雨', '雪', 'sunny', 'cloudy', 'rainy']))
    weather.humidity = draw(st.integers(min_value=0, max_value=100))
    weather.wind_speed = draw(st.floats(min_value=0, max_value=50))
    weather.aqi = draw(st.integers(min_value=0, max_value=500))
    weather.timestamp = datetime.utcnow()
    
    return weather


@st.composite
def contexts(draw, weather=None):
    """Generate valid context data."""
    if weather is None:
        weather = draw(weather_data())
    
    context = {
        'weather': {
            'temperature': weather.temperature,
            'condition': weather.condition,
            'humidity': weather.humidity,
            'wind_speed': weather.wind_speed,
            'aqi': weather.aqi
        },
        'time': draw(st.sampled_from(['morning', 'afternoon', 'evening', 'night'])),
        'user_preferences': {}
    }
    
    return context


class TestPOIIntroductionGenerationProperty:
    """Property-based tests for POI introduction generation."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        poi=pois(),
        context=contexts()
    )
    def test_property_10_poi_introduction_generation(self, app, poi, context):
        """
        Feature: buyu-walking-app, Property 10: 景点介绍生成
        Validates: Requirements 2.3
        
        Property: For any triggered POI introduction flow, the AI generator
        should generate non-empty introduction text content.
        
        This tests that:
        1. Introduction text is generated for any valid POI
        2. Generated text is non-empty
        3. Generation works with various contexts
        """
        with app.app_context():
            # Arrange: Create AI service with mocked OpenAI client
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Mock OpenAI response
            mock_response = Mock()
            mock_response.choices = [Mock()]
            mock_response.choices[0].message = Mock()
            mock_response.choices[0].message.content = f"这是关于{poi.name}的精彩介绍。这个{poi.category}位于美丽的地方，具有深厚的历史文化底蕴。"
            
            with patch.object(service.client.chat.completions, 'create', return_value=mock_response):
                # Act: Generate POI introduction
                introduction = service.generate_poi_introduction(poi, context)
            
            # Assert: Introduction should be non-empty
            assert introduction is not None, \
                "Generated introduction should not be None"
            
            assert isinstance(introduction, str), \
                "Generated introduction should be a string"
            
            assert len(introduction.strip()) > 0, \
                "Generated introduction should not be empty"
            
            # Verify the introduction contains meaningful content
            assert len(introduction) >= 10, \
                f"Generated introduction should have substantial content (at least 10 characters), got {len(introduction)}"
    
    @settings(max_examples=50, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        poi=pois(),
        context=contexts()
    )
    def test_property_10_introduction_generation_with_retry(self, app, poi, context):
        """
        Feature: buyu-walking-app, Property 10: 景点介绍生成
        Validates: Requirements 2.3
        
        Property: The AI generator should retry on failure and eventually
        generate introduction text or raise an exception after max retries.
        
        This tests the retry mechanism.
        """
        with app.app_context():
            # Arrange: Create AI service
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Mock OpenAI to fail first, then succeed
            mock_response = Mock()
            mock_response.choices = [Mock()]
            mock_response.choices[0].message = Mock()
            mock_response.choices[0].message.content = f"这是关于{poi.name}的介绍。"
            
            call_count = [0]
            
            def mock_create(*args, **kwargs):
                call_count[0] += 1
                if call_count[0] < 2:
                    raise Exception("API temporarily unavailable")
                return mock_response
            
            with patch.object(service.client.chat.completions, 'create', side_effect=mock_create):
                # Act: Generate POI introduction (should succeed after retry)
                introduction = service.generate_poi_introduction(poi, context)
            
            # Assert: Should eventually succeed
            assert introduction is not None, \
                "Should generate introduction after retry"
            
            assert len(introduction.strip()) > 0, \
                "Generated introduction should not be empty"
            
            assert call_count[0] >= 2, \
                "Should have retried at least once"
    
    @settings(max_examples=50, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        poi=pois(),
        context=contexts()
    )
    def test_property_10_introduction_generation_max_retries(self, app, poi, context):
        """
        Feature: buyu-walking-app, Property 10: 景点介绍生成
        Validates: Requirements 2.3
        
        Property: The AI generator should raise an exception after max retries
        if generation consistently fails.
        
        This tests the error handling.
        """
        with app.app_context():
            # Arrange: Create AI service
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Mock OpenAI to always fail
            with patch.object(service.client.chat.completions, 'create', side_effect=Exception("API error")):
                # Act & Assert: Should raise exception after max retries
                with pytest.raises(Exception) as exc_info:
                    service.generate_poi_introduction(poi, context)
                
                assert "Failed to generate POI introduction" in str(exc_info.value), \
                    "Should raise appropriate error message"
                
                assert "after 3 attempts" in str(exc_info.value), \
                    "Should indicate max retries reached"


class TestTTSPresentationProperty:
    """Property-based tests for TTS presentation."""
    
    @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        text=st.text(min_size=1, max_size=500, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Zs', 'Nd', 'Po')))
    )
    def test_property_11_tts_presentation(self, app, text):
        """
        Feature: buyu-walking-app, Property 11: TTS呈现
        Validates: Requirements 2.4
        
        Property: For any generated POI introduction text, the system should
        call the TTS service to convert it to speech and return audio bytes.
        
        This tests that:
        1. TTS conversion works for any valid text
        2. Audio bytes are returned
        3. Audio is non-empty
        """
        # Skip empty or whitespace-only text
        assume(text.strip())
        
        with app.app_context():
            # Arrange: Create AI service with mocked OpenAI client
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Mock OpenAI TTS response
            mock_response = Mock()
            mock_response.content = b'fake_audio_data_' + text[:20].encode('utf-8', errors='ignore')
            
            with patch.object(service.client.audio.speech, 'create', return_value=mock_response):
                # Act: Convert text to speech
                audio_bytes = service.text_to_speech(text)
            
            # Assert: Audio bytes should be returned
            assert audio_bytes is not None, \
                "TTS should return audio bytes"
            
            assert isinstance(audio_bytes, bytes), \
                "TTS should return bytes type"
            
            assert len(audio_bytes) > 0, \
                "TTS audio should not be empty"
    
    @settings(max_examples=50, suppress_health_check=[HealthCheck.function_scoped_fixture])
    @given(
        text=st.text(min_size=1, max_size=500, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Zs', 'Nd', 'Po')))
    )
    def test_property_11_tts_with_retry(self, app, text):
        """
        Feature: buyu-walking-app, Property 11: TTS呈现
        Validates: Requirements 2.4
        
        Property: The TTS service should retry on failure and eventually
        convert text to speech or raise an exception after max retries.
        
        This tests the retry mechanism for TTS.
        """
        # Skip empty or whitespace-only text
        assume(text.strip())
        
        with app.app_context():
            # Arrange: Create AI service
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Mock TTS to fail first, then succeed
            mock_response = Mock()
            mock_response.content = b'fake_audio_data'
            
            call_count = [0]
            
            def mock_create(*args, **kwargs):
                call_count[0] += 1
                if call_count[0] < 2:
                    raise Exception("TTS API temporarily unavailable")
                return mock_response
            
            with patch.object(service.client.audio.speech, 'create', side_effect=mock_create):
                # Act: Convert text to speech (should succeed after retry)
                audio_bytes = service.text_to_speech(text)
            
            # Assert: Should eventually succeed
            assert audio_bytes is not None, \
                "Should convert text to speech after retry"
            
            assert len(audio_bytes) > 0, \
                "Audio should not be empty"
            
            assert call_count[0] >= 2, \
                "Should have retried at least once"
    
    @settings(max_examples=20, suppress_health_check=[HealthCheck.function_scoped_fixture])
    def test_property_11_tts_empty_text_validation(self, app):
        """
        Feature: buyu-walking-app, Property 11: TTS呈现
        Validates: Requirements 2.4
        
        Property: The TTS service should validate input and reject empty text.
        
        This tests input validation.
        """
        with app.app_context():
            # Arrange: Create AI service
            service = AIGenerationService(
                api_key='test_key',
                api_base='https://test.api.com/v1'
            )
            
            # Act & Assert: Should raise ValueError for empty text
            with pytest.raises(ValueError) as exc_info:
                service.text_to_speech("")
            
            assert "cannot be empty" in str(exc_info.value).lower(), \
                "Should raise appropriate error for empty text"
            
            # Test whitespace-only text
            with pytest.raises(ValueError) as exc_info:
                service.text_to_speech("   ")
            
            assert "cannot be empty" in str(exc_info.value).lower(), \
                "Should raise appropriate error for whitespace-only text"
